package dbPhase.hypeerweb;
import java.util.*;

/**
 * HyPeerWeb is a collection of nodes that makes it easy to connect the nodes together.
 *
 * @pre Domain
 * 	nodes - ArrayList<Node>
 * 	hyPeerWeb = null
 * 	HyPeerWebDatabase hyPeerWebDatabase
 *
 *
 * @author scott44
 *
 */
public class HyPeerWeb {

	ArrayList<Node> nodes;

	private static HyPeerWeb hyPeerWeb = null;
	private HyPeerWebDatabase hyPeerWebDatabase;

	/**
	 * addNode - simply adds a node to the hypeerweb without the proper connections
	 *
	 * @pre none
	 *
	 * @param node the node to add to the array list of nodes
	 */
	public void addNode(Node node) {
		nodes.add(node);
	}

	/**
	 * Clears the hyPeerWeb and the database
	 *
	 * @pre none
	 *
	 * @post nodes = new ArrayList<Node>()
	 * 		 hyPeerWebDatabase.clearDatabase
	 */
	public void clear() {
		nodes = new ArrayList<Node>();
		hyPeerWebDatabase.clearDatabase();
	}

	/**
	 * simply checks the array list of nodes to see if the passed in node is in the array
	 *
	 * @pre none
	 *
	 * @param node - the node that is being checked if it is in the hypeerwebs nodes
	 *
	 * @return boolean - nodes.contains(node);
	 *
	 */
	public boolean contains(Node node) {
		return nodes.contains(node);
	}

	/**
	 * getHyperwebDataBase - simply returns the singleton hyPeerWebDatabase
	 *
	 * @pre none
	 *
	 * @return the singleton hyPeerWebDatabase
	 */
	public HyPeerWebDatabase getHyperwebDataBase() {
		return hyPeerWebDatabase;
	}

	/**
	 * getNode - gets the node if it is there from the index passed in.
	 *
	 * @pre nodes.size() == 0;
	 *
	 * @param i - the webid in integer format for the node to get.
	 *
	 * @return nodes.get(i);
	 */
	public Node getNode(int i) {
		if (nodes.size() == 0) {
			return null;
		}
//		System.out.println("*****The nodes that are stored in the array list.*****");
//		if(i==0)
//		{
//			System.out.println("webid:" +nodes.get(0).getWebId().getValue());
//			System.out.println("fold:" +nodes.get(0).fold.getWebId().getValue());
//			System.out.println("surfold:" +
//			nodes.get(0).surrogateFold.getWebId().getValue());
//			System.out.println("invsurfold:" +
//			nodes.get(0).inverseSurrogateFold.getWebId().getValue());
//		}
//		if(i==1)System.out.println(nodes.get(1).getWebId().getValue());
//		if(i==2)System.out.println(nodes.get(2).getWebId().getValue());

		for (Node n: nodes) {
			if (n.getWebIdValue() == i) {
				return n;
			}
		}

		System.err.println("ERORR ERROR");

		//TODO - CHANGED THIS TO WORK BETTER WITH OUR TESTS!
		return null;
//		return nodes.get(i);
	}

	/**
	 * getSingleton returns the one instance of the HyPeerWeb. Initializes one if it is null.
	 *
	 * @pre hyPeerWeb is null
	 *
	 * @return hyPeerWeb - whether is had to be constructed or not.
	 */
	public static HyPeerWeb getSingleton() {
		if (hyPeerWeb != null) {
			return hyPeerWeb;
		}
		hyPeerWeb = new HyPeerWeb();
		return hyPeerWeb;

	}

	/**
	 * HyPeerWeb is a private constructor so the class cannot be made outside of here.
	 * getSingleton() controls the making of the class
	 *
	 * @pre nodes = new ArrayList<Node>
	 * 		hyPeerWebDatabase = HyPeerWebDatabase.getSingleton()
	 * 		boolean ok = true
	 * 		int i = 0
	 *
	 */
	private HyPeerWeb() {
		nodes = new ArrayList<Node>();
		hyPeerWebDatabase = HyPeerWebDatabase.getSingleton();

		boolean ok = true;
		int i = 0;
		while (ok) {
			SimplifiedNodeDomain toAdd = hyPeerWebDatabase.getNode(i);
		//	System.out.println("loadedone");
			if (toAdd == null) {
				ok = false;
				break;
			}
			Node converted = new Node(toAdd.webId, toAdd.height);
			nodes.add(converted);
			i++;
		}

		ok = true;
		i = 0;
		while (ok) {
			SimplifiedNodeDomain toAdd = hyPeerWebDatabase.getNode(i);
			if (toAdd == null) {
				ok = false;
				break;
			}
			Node converted = nodes.get(i);


			for (Integer n:toAdd.downPointers) {
				converted.addDownPointer(nodes.get(convertwebidtoindex(n)));
			}
			for (Integer n:toAdd.neighbors) {
				converted.addNeighbor(nodes.get(convertwebidtoindex(n)));
			}
			for (Integer n:toAdd.upPointers) {
				converted.addUpPointer(nodes.get(convertwebidtoindex(n)));
			}

			i++;
		}

	}

	/**
	 * Takes in an Integer webid and goes through the nodes until it gets the node with the same id
	 *
	 * @pre none
	 *
	 * @param webid - integer of the webid to find
	 *
	 * @return the index of the node that equals the webid or zero if it is not found.
	 */
	private int convertwebidtoindex(Integer webid) {
		for (int i = 0; i < nodes.size(); i++) {
			if (nodes.get(i).webId.getValue() == webid) {
				return i;
			}
		}
		return 0;

	}

	/**
	 * Reloads the hyPeerWeb with the default database
	 *
	 * @pre none
	 *
	 * @post HypeerWebDatabase.initHyPeerWebDatabase
	 * 		 nodes = new arraylist
	 * 		 hyPeerWebDatabase.getSingleton.
	 */
	public void reload() {
		HyPeerWebDatabase.initHyPeerWebDatabase("");

		nodes = new ArrayList<Node>();
		hyPeerWebDatabase = HyPeerWebDatabase.getSingleton();

		boolean ok = true;
		int i = 0;
		while (ok) {
			//System.out.println("~~~~");
		//	System.out.println("first node: " + hyPeerWebDatabase.getNode(0));
			SimplifiedNodeDomain toAdd = hyPeerWebDatabase.getNode(i);
//			System.out.println(toAdd);
			if (toAdd == null) {
				ok = false;
				break;
			}
			Node converted = new Node(toAdd.webId, toAdd.height);
			nodes.add(converted);
			i++;
		}

		ok = true;
		i = 0;
		while (ok) {
//			System.out.println("~?~?~");
			SimplifiedNodeDomain toAdd = hyPeerWebDatabase.getNode(i);
			if (toAdd == null) {
				ok = false;
				break;
			}
			Node converted = nodes.get(i);

			for (Integer n:toAdd.downPointers) {
				converted.addDownPointer(nodes.get(convertwebidtoindex(n)));
			}
			for (Integer n:toAdd.neighbors) {
				converted.addNeighbor(nodes.get(convertwebidtoindex(n)));
			}
			for (Integer n:toAdd.upPointers) {
				converted.addUpPointer(nodes.get(convertwebidtoindex(n)));
			}
//			converted.inverseSurrogateFold = nodes.get(i).inverseSurrogateFold;
//			converted.surrogateFold = nodes.get(i).surrogateFold;
//			converted.fold = nodes.get(i).fold;
			if (toAdd.getInverseSurrogateFold() != -1) {
				converted.setInverseSurrogateFold(
						nodes.get(toAdd.getInverseSurrogateFold()));
			}
			else {
				converted.setInverseSurrogateFold(Node.NULL_NODE);
			}
			if (toAdd.getSurrogateFold() != -1) {
				converted.setSurrogateFold(nodes.get(toAdd.getSurrogateFold()));
			}
			else {
				converted.setSurrogateFold(Node.NULL_NODE);
			}
			if (toAdd.getFold() != -1) {
				converted.setFold(nodes.get(toAdd.getFold()));
			}
			else {
				converted.setFold(Node.NULL_NODE);
			}
			i++;
		}
	}

	/**
	 * Reloads the specified database
	 *
	 * @pre none
	 *
	 * @param dbName - the name of the database to be loaded
	 */
	public void reload(String dbName) {
		HyPeerWebDatabase.initHyPeerWebDatabase(dbName);

		nodes = new ArrayList<Node>();
		hyPeerWebDatabase = HyPeerWebDatabase.getSingleton();

		boolean ok = true;
		int i = 0;
		while (ok) {
//			System.out.println("loadedone~?~?~");
			SimplifiedNodeDomain toAdd = hyPeerWebDatabase.getNode(i);
			if (toAdd == null) {
				ok = false;
				break;
			}
			Node converted = new Node(toAdd.webId, toAdd.height);

			nodes.add(converted);
			i++;
		}

		ok = true;
		i = 0;
		while (ok) {
//			System.out.println("~?~?~");
			SimplifiedNodeDomain toAdd = hyPeerWebDatabase.getNode(i);
			if (toAdd == null) {
				ok = false;
				break;
			}
			Node converted = nodes.get(i);

			for (Integer n:toAdd.downPointers) {
				converted.addDownPointer(nodes.get(convertwebidtoindex(n)));
			}
			for (Integer n:toAdd.neighbors) {
				converted.addNeighbor(nodes.get(convertwebidtoindex(n)));
			}
			for (Integer n:toAdd.upPointers) {
				converted.addUpPointer(nodes.get(convertwebidtoindex(n)));
			}
//			converted.inverseSurrogateFold = nodes.get(i).inverseSurrogateFold;
//			converted.surrogateFold = nodes.get(i).surrogateFold;
//			converted.fold = nodes.get(i).fold;
			if (toAdd.getInverseSurrogateFold() != -1) {
				converted.setInverseSurrogateFold(
						nodes.get(toAdd.getInverseSurrogateFold()));
			}
			else {
				converted.setInverseSurrogateFold(Node.NULL_NODE);
			}
			if (toAdd.getSurrogateFold() != -1) {
				converted.setSurrogateFold(nodes.get(toAdd.getSurrogateFold()));
			}
			else {
				converted.setSurrogateFold(Node.NULL_NODE);
			}
			if (toAdd.getFold() != -1) {
				converted.setFold(nodes.get(toAdd.getFold()));
			}
			else {
				converted.setFold(Node.NULL_NODE);
			}
			i++;
			//System.out.println(converted.fold.webId);
		}

//		System.out.println("=============Reload");


	}

	/**
	 * removeNode - removes the node that is passed in from the array list
	 *
	 * @pre none
	 *
	 * @post nodes.remove(node)
	 *
	 * @param node - the node to be removed.
	 *
	 *
	 */
	public void removeNode(Node node) {
		nodes.remove(node);
	}

	/**
	 * Saves all nodes into the database
	 *
	 * @pre none
	 *
	 * @post converts all the nodes to simplifiedNodedomain and saves them to the database
	 */
	public void saveToDatabase() {
		HashSet<SimplifiedNodeDomain> simpleNodes = new HashSet<SimplifiedNodeDomain>();
		for (Node n: nodes) {
			simpleNodes.add(n.constructSimplifiedNodeDomain());
//			if(n.getWebId().getValue()==1)
//			{
//				System.out.println("%%%%%%%%%%%%%%%%%%%%%%%%%");
//				System.out.println(n.fold.getWebId().getValue());
//				System.out.println(n.constructSimplifiedNodeDomain().fold);
//			}
		}
		hyPeerWebDatabase.saveNodes(simpleNodes);
	}

	/**
	 * size returns the number of nodes in the hypeerweb
	 *
	 * @pre none
	 *
	 * @return int nodes.size()
	 */
	public int size() {
		return nodes.size();

	}

	/*------------------------------Phase 2----------------------------------------*/
	/**
	 * addToHyPeerWeb a test to make sure a node is passed in not just any object
	 *
	 * @pre none
	 *
	 * @post An error message saying this method should not be called.
	 *
	 * @param node - the node to add
	 *
	 * @param startNode of type object
	 */
	public void addToHyPeerWeb(Node node, Object startNode) {
		//empty method on purpose
		System.err.println("Shouldn't be called...");
	}
	// uses hypeerweb to use node to add node.
	/**
	 * addToHyPeerWeb adds the node from the node startnode.
	 *
	 * @pre none
	 *
	 * @param node - the node to add to the hypeerweb
	 *
	 * @param startNode - the node to start the process to find the insert location.
	 */
	public void addToHyPeerWeb(Node node, Node startNode) {
		if (startNode == null) {
			nodes.add(node); // If null, I think that it is the first node.
			node.nodeState = HypercubeCapState.getSingleton();
		}
		else {
			if(node.getWebIdValue()>=0){   //~~~~~~~~`
				AddOrRemoveNode add = new AddNode();
				add.execute(startNode, node);
			}   //~~~~~~~~~
		}
	}

	private class AddNode extends AddOrRemoveNode {
		AddNode() {

		}
		@Override
		public Node findPoint(Node terminal) {
			if (terminal.nodeState == HypercubeCapState.getSingleton()) {
				return terminal.fold;
			}
			return terminal.nodeState.search(terminal.getSmallestNeighbor().getWebIdValue(),
					terminal.getSmallestDownPointer().getWebIdValue(), terminal);
		}
		@Override
		public void makeChanges(Node point, Node change) {
			point.addChild(change);
		}
	}

	public void removeFromHyPeerWeb(Node node) {
		if (node != null) {
			int id = node.getWebIdValue();
			node.removeFromHyPeerWeb();
			nodes.remove(node);
			Node lastnode = nodes.get(nodes.size() - 1);
			nodes.remove(node);
			nodes.add(id, lastnode);
		}
	}

	public Iterator<Node> iterator() {
		return nodes.iterator();
	}
}
